// Server.cpp : 定义控制台应用程序的入口点。
// g++编译需加入编译选项 -lwsock32

// #include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <Winsock2.h>
#include <process.h>
#include "conio.h"

#pragma comment(lib, "ws2_32.lib")

// define constant
#define QLEN 5
#define STRLEN 4096
#define SERVPORT 5400
#define WSVERS MAKEWORD(2, 2)
#define USER_NOT_EXISTS 400
#define INCORRECT_PASSWORD 401
#define USER_NOT_FOUND 4040
#define NOT_ONLINE 4041
#define REJECT_CHATTING 4042
#define USER_IS_LOCK 4043
#define IS_ONLINE 2000
#define START_CHATTING 2001
#define MAXCLNT 64
#define CLIUSERLEN 8

HANDLE  hThread[MAXCLNT];        // cli thread pool
int     cln_count = 0;
HANDLE  chatRequThread[MAXCLNT]; // thread pool for chatting: request
HANDLE  chatDestThread[MAXCLNT]; // thread pool for chatting: destination
int     chat_count = 0;
struct SockInfo {
    SOCKET  sock;
    int     userId;
} cliSock[MAXCLNT];             // socket pool
struct User {
    int     valid;
    char    username[CLIUSERLEN + 1];
    char    password[CLIUSERLEN + 1];
    int     online;
    int     lock;                  // waiting thread lock
    SOCKET  sock;
    struct  sockaddr_in addr;
} CliUser[MAXCLNT];            // user infomation
int group_count = 0;
struct Group {
    int  userId[MAXCLNT];
    int  num;
    int  quitState;            // user will quit group
} chatGroup[MAXCLNT];
HANDLE chatGroupThread[MAXCLNT]; // thread for group chatting
int chat_group_count = 0;

unsigned __stdcall send_to_all(void * linksock);

void errExit(const char* errStr, int num = 0) {
    printf(errStr, num);
    return;
}

void testSend(int messageLen);

void initUser();

void initGroup();

int findUser(char * message);

int userLogIn(SOCKET linksock, struct sockaddr_in addr);

int getSockUserId(SOCKET linksock);

void writeUserListToBuf(char * buf, int num);

void chat(int userID);

void oneToOneChatStart(int reqId, int destId);

unsigned __stdcall sendToOne(void * userID);

void list(SOCKET linksock);

unsigned __stdcall sendToGroup(void * ID);

void groupChat(int userID);

void transportFile(int reqId, int destId);

void call(int userID);

int main(int argc, char **argv) {
    initUser();
    initGroup();
    struct  sockaddr_in localSockIn, fromSockIn;
    SOCKET  masterSock;
    WSADATA wsadata;
    int     addrLen;
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    int     messageLen;
    if (WSAStartup(WSVERS, &wsadata) != 0)
        errExit("WSAStartup failed\n");

    // create socket
    masterSock = socket(PF_INET, SOCK_STREAM, 0);
    if (masterSock == INVALID_SOCKET) {
        errExit("failed socket, error number: %d\n", WSAGetLastError());
    }

    // server address
    localSockIn.sin_family = AF_INET;
    localSockIn.sin_port = htons(SERVPORT);
    localSockIn.sin_addr.s_addr = htonl(INADDR_ANY);

    // binding
    if (bind(masterSock, (SOCKADDR*)&localSockIn, sizeof(localSockIn)) == SOCKET_ERROR) {
        errExit("bind() failed: %d\n", WSAGetLastError());
    }

    // listen
    if (listen(masterSock, QLEN) == SOCKET_ERROR) {
        errExit("listen() failed: %d\n", WSAGetLastError());
    }

    // client address length
    addrLen = sizeof(fromSockIn);

    // wait for connection from client...
    while (true) {
        if (cln_count < MAXCLNT) {
            printf("port %d is waiting for connection from %s...\n",
                ntohs(localSockIn.sin_port), inet_ntoa(localSockIn.sin_addr));

            cliSock[cln_count].sock = accept(masterSock, (SOCKADDR*)&fromSockIn, &addrLen);
            if (cliSock[cln_count].sock == INVALID_SOCKET) {
                errExit("eccept() failed: %d\n", WSAGetLastError());
            }
            printf("accept a client whose IP is %s port %d. \n",
                inet_ntoa(fromSockIn.sin_addr), ntohs(fromSockIn.sin_port));

            // a cli log in
            int userId = userLogIn(cliSock[cln_count].sock, fromSockIn);
            if (userId < 0) {
                // log in failed
                continue;
            }
            cliSock[cln_count].userId = userId;
            CliUser[userId].lock = 0; // not in locked state
            CliUser[userId].sock = cliSock[cln_count].sock;

            // send user list to new cli
            memset(wBuf, 0, sizeof(wBuf));
            writeUserListToBuf(wBuf, cln_count);
            messageLen = send(cliSock[cln_count].sock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);

            // tell to all clis, including latest one
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "user %s from IP: %s port number: %d log in. \n",
                CliUser[userId].username, inet_ntoa(fromSockIn.sin_addr), fromSockIn.sin_port);
            for (int i = 0; i <= cln_count; ++i) {
                messageLen = send(cliSock[i].sock, wBuf, strlen(wBuf), 0);
                testSend(messageLen);
            }
            // put this socket into thread pool
            hThread[cln_count] = (HANDLE)_beginthreadex(NULL, 0, &send_to_all, (void *)&cliSock[cln_count], 0, NULL);
            cln_count++;
        }
        else {
            // too much users
            printf("There are too much clis online.\n");
            break;
        }
    }

    WaitForMultipleObjects(cln_count, hThread, TRUE, INFINITE);
    closesocket(masterSock);
    // close thread
    for (int i = 0; i < cln_count; i++) {
        CloseHandle(hThread[i]);
    }
    WSACleanup();

    return 0;
}

unsigned __stdcall send_to_all(void * linksock) {
    int     sock_index;
    struct  sockaddr_in cln_addr;
    int     cln_addr_len;
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    SOCKET  lsock = *((SOCKET*)linksock);
    int     recvlen;
    int     messageLen;

    // get sock_addr
    int userId  = getSockUserId(lsock);
    if (lsock < 0) return 0;
    memset(&cln_addr, 0, sizeof(cln_addr));
    cln_addr     = CliUser[userId].addr;
    cln_addr_len = sizeof(cln_addr);

    while (true) {

        while (CliUser[userId].lock) {
            // user is busy
            continue;
        }

        memset(rBuf, 0, sizeof(rBuf));
        recvlen = recv(lsock, rBuf, STRLEN, 0);
        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            // force downline
            memset(wBuf, 0, sizeof(wBuf));
            // change online state
            CliUser[userId].online = 0;
            sprintf(wBuf, "user %s from IP: %s port: %d log out. \n",
                CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
            printf("%s", wBuf);
            CliUser[userId].online = 0;
            for (int i = 0; i < cln_count; i++) {
                if (cliSock[i].sock != lsock) {
                    messageLen = send(cliSock[i].sock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                }
                else
                    sock_index = i;
            }
            break;
        }
        else if (recvlen > 0) {
            rBuf[recvlen] = '\0';

            if (strncmp(rBuf, "quit", 5) == 0) {
                // quit program
                memset(wBuf, 0, sizeof(wBuf));
                // change online state
                CliUser[userId].online = 0;
                sprintf(wBuf, "user %s from IP: %s port: %d log out. \n",
                    CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
                printf("%s", wBuf);
                CliUser[userId].online = 0;
                // inform all users
                for (int i = 0; i < cln_count; i++) {
                    if (cliSock[i].sock != lsock) {
                        messageLen = send(cliSock[i].sock, wBuf, strlen(wBuf), 0);
                        testSend(messageLen);
                    }
                    else
                        sock_index = i;
                }

                break;
            }
            else if (!CliUser[userId].lock && strncmp(rBuf, "chat", 5) == 0) {
                // take a chat request
                chat(userId);
            }
            else if (strncmp(rBuf, "list", 5) == 0) {
                // take a list-user request
                list(lsock);
            }
            else if (strncmp(rBuf, "call", 5) == 0) {
                // take a file transport request
                call(userId);
            }

            /*          else {
            // normal rBuf
            memset(&wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "user %s from IP: %s port: %d say: %s",
            CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port, rBuf);
            printf("%s", wBuf);
            // send to all users
            for (int i = 0; i < cln_count; i++) {
            messageLen = send(cliSock[i].sock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            }
            } */
        }
    }

    // move the sockets behind the log-out one forward
    for (int i = sock_index; i < cln_count - 1; i++) {
        cliSock[i].sock = cliSock[i + 1].sock;
        cliSock[i].userId = cliSock[i + 1].userId;
    }
    cln_count--;

    closesocket(lsock);
    return 0;
}

void testSend(int messageLen) {
    switch (messageLen) {
    case SOCKET_ERROR: errExit("send() failed: %d\n", WSAGetLastError()); break;
    case 0: errExit("send() failed: %d\n", WSAGetLastError()); break;
    default: break;//printf("send() successfully. \n", messageLen);
    }
    return;
}

void initUser() {
    CliUser[0].valid = 1;
    strncpy(CliUser[0].username, "test", 5);
    strncpy(CliUser[0].password, "test", 5);
    CliUser[0].online = 0;
    CliUser[1].valid = 1;
    strncpy(CliUser[1].username, "chenxf", 7);
    strncpy(CliUser[1].password, "chenxf", 7);
    CliUser[1].online = 0;
    CliUser[2].valid = 1;
    strncpy(CliUser[2].username, "remote", 7);
    strncpy(CliUser[2].password, "remote", 7);
    CliUser[2].online = 0;
    CliUser[3].valid = 1;
    strncpy(CliUser[3].username, "remote", 7);
    strncpy(CliUser[3].password, "remote", 7);
    CliUser[3].online = 0;
    CliUser[4].valid = 1;
    strncpy(CliUser[4].username, "cafe", 5);
    strncpy(CliUser[4].password, "cafe", 5);
    CliUser[4].online = 0;
    CliUser[5].valid = 1;
    strncpy(CliUser[5].username, "alpha", 6);
    strncpy(CliUser[5].password, "alpha", 6);
    CliUser[5].online = 0;
    CliUser[6].valid = 1;
    strncpy(CliUser[6].username, "beta", 5);
    strncpy(CliUser[6].password, "beta", 5);
    CliUser[6].online = 0;
    CliUser[7].valid = 1;
    strncpy(CliUser[7].username, "gamma", 6);
    strncpy(CliUser[7].password, "gamma", 6);
    CliUser[7].online = 0;
    for (int i = 8; i < MAXCLNT; ++i) {
        CliUser[i].valid = 0;
    }
    return;
}

// find the user-id in the user-data-table
int findUser(char * message) {
    for (int i = 0; i < MAXCLNT; ++i) {
        if (CliUser[i].valid) {
            if (strstr(message, CliUser[i].username) != NULL)
                return i;
        }
    }

    // user not found
    return MAXCLNT + 1;
}

int userLogIn(SOCKET linksock, struct sockaddr_in addr) {
    SOCKET  lsock = linksock;
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    struct  sockaddr_in cln_addr = addr;
    int     cln_addr_len = sizeof(cln_addr);
    int     messageLen;
    int     recvlen;

    // recv username
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        memset(&wBuf, 0, sizeof(wBuf));
        sprintf(wBuf, "user from IP: %s port: %d log in failed. \n",
            inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
        printf("%s", wBuf);
        return -1;
    }
    else if (recvlen > 0) {
        // get username
        // rBuf[recvlen] = '\0';
        int userId = findUser(rBuf);
        // check whether user is online
        if (CliUser[userId].online) {
            // reply USER_IS_LOCK
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", USER_IS_LOCK);
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            return -1;
        }
        if (userId < MAXCLNT) {

            // find the user, request for password
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "user %s from IP: %s port: %d. Please Input password: ",
                CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);

            // get password
            memset(rBuf, 0, sizeof(rBuf));
            recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);

            if (recvlen == 0 || recvlen == SOCKET_ERROR) {
                // get password error
                memset(wBuf, 0, sizeof(wBuf));
                sprintf(wBuf, "user %s from IP: %s port: %d log in failed: get-password error. \n",
                    CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
                printf("%s", wBuf);
                return -1;
            }
            else if (recvlen > 0) {
                // got password, confirm it
                // rBuf[recvlen] = '\0';
                if (strstr(rBuf, CliUser[userId].password) != NULL) {
                    // correct password
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "user %s from IP: %s port: %d log in. \n",
                        CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
                    printf("%s", wBuf);

                    // log in success, reply the user
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    // change user status
                    CliUser[userId].online = 1;
                    CliUser[userId].addr.sin_family = AF_INET;
                    CliUser[userId].addr.sin_addr = cln_addr.sin_addr;
                    CliUser[userId].addr.sin_port = cln_addr.sin_port;
                    return userId;
                }
                else {
                    // wrong password
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "incorrect password: user %s from IP: %s port: %d log in failed. \n",
                        CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
                    printf("%s", wBuf);
                    // reply INCORRECT_PASSWORD(401)
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", INCORRECT_PASSWORD);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    return -1;
                }
            }
        }
        else {
            // username not found
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "user not exists: user %s from IP: %s port: %d log in failed. \n",
                rBuf, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
            printf("%s", wBuf);
            // reply USER_NOT_EXISTS(400)
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", USER_NOT_EXISTS);
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            return -1;
        }
    }

    return -1;
}

int getSockUserId(SOCKET linksock) {
    for (int i = 0; i < MAXCLNT; ++i) {
        if (linksock == cliSock[i].sock)
            return cliSock[i].userId;
    }
    return -1;
}

// to list all users online
void writeUserListToBuf(char * buf, int num) {
    strcpy(buf, "online user list: \n");
    int i;
    for (i = 0; i < num; ++i) {
        if (CliUser[cliSock[i].userId].online) {
            strncat(buf, CliUser[cliSock[i].userId].username, CLIUSERLEN);
            strcat(buf, "\n");
        }
    }
    char iToStr[4];
    sprintf(iToStr, "%d", i);
    strcat(buf, iToStr);
    strcat(buf, " in total. \n");

    return;
}

void chat(int userID) {
    int     userId = userID;
    SOCKET  lsock = CliUser[userId].sock;
    char    wBuf[STRLEN];
    char    rBuf[STRLEN];
    int     messageLen;
    int     recvlen;

    // block recv thread in cli
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "If you do want to chat, ");
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // ask chatting type: one-to-one or one-to-more
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "Send 'one' to start one-to-one chatting or 'group' to start more-to-more chatting : ");
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // recv chatting-type response
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("start chatting failed: choice not received. \n");
        return;
    }
    else if (recvlen > 0) {
        rBuf[recvlen] = '\0';
        if (strstr(rBuf, "one") != NULL) {
            // one-to-one chatting

            // request for who it'll chatting with
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "send the user's name you'll chat with: ");
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);

            // got destination username
            memset(rBuf, 0, sizeof(rBuf));
            recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
            rBuf[recvlen - 1] = '\0';
            if (recvlen == 0 || recvlen == SOCKET_ERROR) {
                printf("start chatting failed: dest user not received. \n");
                return;
            }
            else if (recvlen > 0) {
                // find the user
                rBuf[recvlen] = '\0';
                int destUserId = findUser(rBuf);

                // user is busy
                if (CliUser[destUserId].lock) {
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", USER_IS_LOCK);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    printf("user %s is busy now. \n", rBuf);
                    return;
                }

                // can not chat with oneself
                if (destUserId == userId) {
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", USER_IS_LOCK);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    printf("user %s is itself. \n", rBuf);
                    return;
                }

                if (destUserId > MAXCLNT) {
                    // user not found
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", USER_NOT_FOUND);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    printf("user %s is not found. \n", rBuf);
                    return;
                }

                if (CliUser[destUserId].online) {
                    // destination user is online
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", IS_ONLINE);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    printf("user %s is online. \n", rBuf);

                    // begin chatting forward
                    CliUser[userId].lock = 1;
                    CliUser[destUserId].lock = 1; // locked
                    oneToOneChatStart(userId, destUserId);
                    CliUser[userId].lock = 0;
                    CliUser[destUserId].lock = 0; // unlocked
                    return;
                }
                else {
                    // destination user isn't online
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", NOT_ONLINE);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    printf("user %s isn't online. \n", rBuf);
                    return;
                }
            }
        }
        else if (strstr(rBuf, "group") != NULL) {
            // more-to-more chatting
            CliUser[userId].lock = 1; // lock
            groupChat(userId);
            CliUser[userId].lock = 0; // lock
        }
    }

    return;
}

void oneToOneChatStart(int reqId, int destId) {
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    SOCKET  reqSock = CliUser[reqId].sock;
    SOCKET  destSock = CliUser[destId].sock;
    int     messageLen;
    int     recvlen;

    // tell dest user
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "user %s want to chat with you. Send 'chat confirm' to start it or 'chat reject' instead. \n",
        CliUser[reqId].username);
    messageLen = send(destSock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // wait for response from dest user
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(destSock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("error in starting chatting. \n");
        return;
    }
    else if (recvlen > 0) {
        if (strstr(rBuf, "chat confirm") != NULL) {
            // tell req user that chat starting
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", START_CHATTING);
            messageLen = send(reqSock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            printf("%s begin chat with %s...\n", CliUser[reqId].username,
                CliUser[destId].username);

            // start two thread to forward message
            int chatId = chat_count;
            int id1[2], id2[2];
            id1[0] = reqId; id1[1] = destId;
            id2[0] = destId; id2[1] = reqId;
            chatRequThread[chat_count] = (HANDLE)_beginthreadex(NULL, 0, &sendToOne, (void *)&id1, 0, NULL);
            chatDestThread[chat_count] = (HANDLE)_beginthreadex(NULL, 0, &sendToOne, (void *)&id2, 0, NULL);
            chat_count++;

            WaitForSingleObject(chatRequThread[chatId], INFINITE);
            WaitForSingleObject(chatDestThread[chatId], INFINITE);

            // end chatting
            // move the thread behind the log-out one forward
            CloseHandle(chatRequThread[chatId]);
            CloseHandle(chatDestThread[chatId]);
            for (int i = chatId; i < chat_count - 1; i++) {
                chatRequThread[i] = chatRequThread[i + 1];
                chatDestThread[i] = chatDestThread[i + 1];
            }
            chat_count--;
            return;
        }
        else {
            // tell req user that chat reject
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", REJECT_CHATTING);
            messageLen = send(reqSock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            return;
        }
    }

    return;
}

// in one-to-one chatting
unsigned __stdcall sendToOne(void * userID) {
    int     userIdRecv = ((int*)userID)[0];
    int     userIdSend = ((int*)userID)[1];
    char    recvUserName[CLIUSERLEN + 1];
    char    sendUserName[CLIUSERLEN + 1];
    strncpy(recvUserName, CliUser[userIdRecv].username, strlen(CliUser[userIdRecv].username) + 1);
    strncpy(sendUserName, CliUser[userIdSend].username, strlen(CliUser[userIdSend].username) + 1);
    SOCKET  recvSock = CliUser[userIdRecv].sock;
    SOCKET  sendSock = CliUser[userIdSend].sock;
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    int     recvlen;
    int     messageLen;

    while (true) {
        memset(rBuf, 0, sizeof(rBuf));
        recvlen = recv(recvSock, rBuf, sizeof(rBuf), 0);

        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "user %s leave chatting %s & %s. \n",
                recvUserName, recvUserName, sendUserName);
            printf("%s", wBuf);
            messageLen = send(sendSock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            // chatting end
            break;
        }
        else if (recvlen > 0) {
            rBuf[recvlen] = '\0';

            if (strncmp(rBuf, "quit\n", 7) == 0) {
                // quit
                memset(wBuf, 0, sizeof(wBuf));
                sprintf(wBuf, "user %s leave chatting %s : %s. Please 'quit'(if you still in). \n",
                    recvUserName, recvUserName, sendUserName);
                printf("%s", wBuf);
                messageLen = send(sendSock, wBuf, strlen(wBuf), 0);
                testSend(messageLen);
                return 1;
            }
            else {
                // normal message
                memset(wBuf, 0, sizeof(wBuf));
                sprintf(wBuf, "%s: %s", recvUserName, rBuf);
                // printf("%s", wBuf);
                messageLen = send(sendSock, wBuf, strlen(wBuf), 0);
                testSend(messageLen);
            }
        }
    }

    return 1;
}

void list(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    wBuf[STRLEN];
    int     messageLen;

    // get all users online
    memset(wBuf, 0, sizeof(wBuf));
    writeUserListToBuf(wBuf, cln_count);
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    return ;
}

void initGroup() {
    for (int i = 0; i < MAXCLNT; ++i) {
        chatGroup[i].num = 0;
    }
}

void groupChat(int userID) {
    int     userId = userID;
    SOCKET  lsock = CliUser[userId].sock;
    char    wBuf[STRLEN];
    char    rBuf[STRLEN];
    int     messageLen;
    int     recvlen;
    int     gCount = group_count;
    int     id[2];
    id[1] = gCount;
    group_count++;

    // set the group owner
    chatGroup[gCount].userId[chatGroup[gCount].num] = userId;
    chatGroup[gCount].num++;
    chatGroup[gCount].quitState = 1;

    // request for group members' names
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "Please input group member(one in a line, end with '.'):\n");
    printf("%s", wBuf);
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // recv group members' names
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    int cnt = 0;
    while (recvlen != 0 && recvlen != SOCKET_ERROR && strncmp(rBuf, ".", 1) != 0) {
        rBuf[recvlen - 1] = '\0';
        printf("%s, ", rBuf);
        id[0] = findUser(rBuf);
        if (id[0] < MAXCLNT && !CliUser[id[0]].lock && id[0] != userId) {
            chatGroup[gCount].userId[chatGroup[gCount].num] = id[0];
            CliUser[id[0]].lock = 1;
            chatGroup[gCount].num++;
        }
        id[1] = gCount;
        chatGroupThread[cnt++] = (HANDLE)_beginthreadex(NULL, 0, &sendToGroup, (void *)&id, 0, NULL);
        recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    }

    // notice group members
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "%s invite you to join a group, reply 'group' to start: ",
        CliUser[userId].username);
    for (int i = 1; i < chatGroup[gCount].num; ++i) {
        messageLen = send(CliUser[chatGroup[gCount].userId[i]].sock, wBuf, strlen(wBuf), 0);
        testSend(messageLen);
    }

    // group-chatting begin
    id[0] = userId;
    id[1] = gCount;
    chatGroupThread[cnt++] = (HANDLE)_beginthreadex(NULL, 0, &sendToGroup, (void *)id, 0, NULL);

    WaitForMultipleObjects(chatGroup[gCount].num, chatGroupThread, TRUE, INFINITE);
    // close thread
    for (int i = 0; i < chatGroup[gCount].num; i++) {
        CloseHandle(chatGroupThread[i]);
    }

    // unlock
    for (int i = 1; i < chatGroup[gCount].num; ++i) {
        CliUser[chatGroup[gCount].userId[i]].lock = 0;
    }

    chat_group_count = 0;
    chatGroup[gCount].num = 0;
    group_count--;
    return;
}

// in group-chatting
unsigned __stdcall sendToGroup(void * ID) {
    int userId = ((int*)ID)[0];
    int gCount = ((int*)ID)[1];
    SOCKET lsock = CliUser[userId].sock;
    char rBuf[STRLEN];
    char wBuf[STRLEN];
    int recvlen;
    int messageLen;
    // printf("%d in %d group chat begin\n", userId, gCount);

    while (true) {
        memset(rBuf, 0, sizeof(rBuf));
        recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);

        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            printf("group chat error.\n");
            return 1;
        }
        else {
            rBuf[recvlen] = '\0';

            if (strncmp(rBuf, "quit group\n", 12) == 0) {
                printf("%s quit group chatting.\n", CliUser[userId].username);
                if (chatGroup[gCount].quitState) {
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "quit group");
                    for (int i = 0; i < chatGroup[gCount].num; ++i) {
                        messageLen = send(CliUser[chatGroup[gCount].userId[i]].sock, wBuf, strlen(wBuf), 0);
                        testSend(messageLen);
                    }
                    // end group chatting
                    chatGroup[gCount].quitState = 0;
                    // group owner
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "send 'quit group confirm' to leave group: ");
                    messageLen = send(CliUser[chatGroup[gCount].userId[0]].sock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                }
                return 0;
            }
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%s: %s", CliUser[userId].username, rBuf);
            printf("%s", wBuf);

            // send to group
            for (int i = 0; i < chatGroup[gCount].num; ++i) {
                messageLen = send(CliUser[chatGroup[gCount].userId[i]].sock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
            }
        }
    }

    return 0;
}

void call(int userID) {
    int     userId = userID;
    SOCKET  lsock = CliUser[userId].sock;
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    int     messageLen;
    int     recvlen;

    // for file transport
    // ask which one called
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "send user(name) you want to call: ");
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("call error: failed recv username. \n");
        return ;
    } else if (recvlen > 0) {
        rBuf[recvlen - 1] = '\0'; printf("%s %d", rBuf, recvlen);
        int destUserId = findUser(rBuf);

        if (destUserId > MAXCLNT) {
            // user not found
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", USER_NOT_FOUND);
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            printf("user %s is not found. \n", rBuf);
            return;
        }

        // user is busy
        if (CliUser[destUserId].lock) {
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", USER_IS_LOCK);
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            printf("user %s is busy now. \n", rBuf);
            return;
        }

        // can not call oneself
        if (destUserId == userId) {
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", USER_IS_LOCK);
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            printf("user %s is itself. \n", rBuf);
            return;
        }

        if (CliUser[destUserId].online) {
            // destination user is online
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", IS_ONLINE);
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            printf("user %s is online. \n", rBuf);

            // begin file transport forward
            CliUser[destUserId].lock = 1; // locked
            transportFile(userId, destUserId);
            CliUser[destUserId].lock = 0; // unlocked
            return;
        }
        else {
            // destination user isn't online
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", NOT_ONLINE);
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            printf("user %s isn't online. \n", rBuf);
            return;
        }
    }

    return ;
}

void transportFile(int reqId, int destId) {
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    SOCKET  reqSock = CliUser[reqId].sock;
    SOCKET  destSock = CliUser[destId].sock;
    int     messageLen;
    int     recvlen;

    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "user %s want to call you. Send 'call confirm' to start it. \n",
        CliUser[reqId].username);
    messageLen = send(destSock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(destSock, rBuf, sizeof(rBuf), 0);
        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("error in starting chatting. \n");
        return;
    }
    else if (recvlen > 0) {
        if (strstr(rBuf, "call confirm") != NULL) { printf("%s", rBuf);
            // tell req user that chat starting
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", START_CHATTING);
            printf("%s call %s...\n", CliUser[reqId].username,
                CliUser[destId].username);

            // wait for port from destination
            memset(rBuf, 0, sizeof(rBuf));
            recvlen = recv(destSock, rBuf, sizeof(rBuf), 0);
            if (recvlen == 0 || recvlen == SOCKET_ERROR) {
                printf("port recv error.\n");
                return ;
            } else if (recvlen > 0) {
                // send port
                memset(wBuf, 0, sizeof(wBuf));
                strncpy(wBuf, rBuf, recvlen + 1);
                messageLen = send(reqSock, wBuf, strlen(wBuf), 0);
                testSend(messageLen); printf("%s port send.\n", wBuf);
                // again
                memset(wBuf, 0, sizeof(wBuf));
                strncpy(wBuf, rBuf, recvlen + 1);
                messageLen = send(reqSock, wBuf, strlen(wBuf), 0);
                testSend(messageLen); printf("%s port send.\n", wBuf);
                // send ip
                memset(wBuf, 0, sizeof(wBuf));
                sprintf(wBuf, "%s", inet_ntoa(CliUser[destId].addr.sin_addr));
                messageLen = send(reqSock, wBuf, strlen(wBuf), 0);
                testSend(messageLen); printf("start transport.\n");

                // wait for end
                memset(rBuf, 0, sizeof(rBuf));
                recvlen = recv(reqSock, rBuf, sizeof(rBuf), 0); 
                if (recvlen > 0) printf("%s\n", rBuf);
            }
        }
    }

    return ;
}

